package org.zjvis.datascience.common.algo;

import cn.hutool.core.lang.Pair;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zjvis.datascience.common.enums.AlgEnum;
import org.zjvis.datascience.common.enums.EngineEnum;
import org.zjvis.datascience.common.sql.SqlHelper;
import org.zjvis.datascience.common.util.ToolUtil;
import org.zjvis.datascience.common.vo.TaskVO;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * @description 通用算子模板父类
 * @date 2021-12-24
 */
public class BaseAlg {

    private final static Logger logger = LoggerFactory.getLogger("BaseAlg");

    protected String engineName = "madlib";

    protected final static String ID_COL = "_record_id_";

    protected final static String FEATURE_COLS = "newFeatures";

    protected final static String ID_TYPE = "integer";

    protected final static int SAMPLE_NUMBER = 1000;

    private String name;

    private int subType;

    private String subTypeName;

    protected int maxParentNumber = -1;

    public String getName() {
        return name;
    }

    public String getSubTypeName() {
        return subTypeName;
    }

    public int getSubType() {
        return subType;
    }

    public int getMaxParentNumber() {
        return maxParentNumber;
    }

    public BaseAlg(String name, int subType, String subTypeName) {
        this.name = name;
        this.subType = subType;
        this.subTypeName = subTypeName;
    }

    public String initSql(JSONObject json, List<SqlHelper> helpers, long timeStamp) {
        //针对自定义算子的
        return initSql(json, helpers, timeStamp, engineName);
    }

    public String initSql(JSONObject json, List<SqlHelper> helpers, long timeStamp, String engineName) {
        //其他通用算子的
        return "";
    }

    //load the template file containing parameters and their tips
    public JSONArray getTemplateParamList(String fileName) {
        String json = new ToolUtil().readJsonFile(fileName);
        if (json.length() == 0) {
            return null;
        }
        return JSONArray.parseArray(json);
    }

    protected void setSubTypeForOutput(JSONObject json) {
        json.put("subType", this.subType);
    }

    protected void baseInitTemplate(JSONObject data) {
        data.put("algName", this.getName());
        data.put("subType", this.getSubType());
        data.put("subTypeName", this.getSubTypeName());
        data.put("parentType", new JSONArray());
        data.put("input", new JSONArray());
        data.put("output", new JSONArray());
        data.put("maxParentNumber", maxParentNumber);
    }

    public void initTemplate(JSONObject data) {
    }

    public String getFeatureColsStr(JSONArray features) {
        StringBuffer featureCols = new StringBuffer();
        if (features == null || features.size() == 0) {
            return null;
        }
        for (int i = 0; i < features.size(); ++i) {
            String feature = features.getString(i);
            if (feature.contains(".")) {
                String[] tmps = feature.split("\\.");
                feature = tmps[tmps.length - 1];
            }
            featureCols.append(String.format("%s", feature));
            if (i != features.size() - 1) {
                featureCols.append(",");
            }
        }
        return featureCols.toString();
    }

    /**
     * 定义每个算子的输出表格式
     *
     * @param vo
     * @return
     */
    public void defineOutput(TaskVO vo) {
    }

    public static BaseAlg instance(JSONObject conf) {
        int type = conf.getIntValue("algType");
        if (AlgEnum.DBSCAN.getVal() == type) {
            return new DBscanAlg();
        } else if (AlgEnum.KMEANS.getVal() == type) {
            return new KmeansAlg();
        } else if (AlgEnum.PCA_DENSE.getVal() == type) {
            return new PcaDenseAlg();
        } else if (AlgEnum.TSNE.getVal() == type) {
            return new TsneAlg();
        } else if (AlgEnum.LLE.getVal() == type) {
            return new LLEAlg();
        } else if (AlgEnum.STAT_ANOMALY.getVal() == type) {
            return new StatisticsAnomalyAlg();
        } else if (AlgEnum.ISO_FOREST.getVal() == type) {
            return new IsolateForestAlg();
        } else if (AlgEnum.LINEARREGRE.getVal() == type) {
            return new LinearRegressionAlg();
        } else if (AlgEnum.LOGREGRE.getVal() == type) {
            return new LogisticRegressionAlg();
        } else if (AlgEnum.FP_GROWTH.getVal() == type) {
            return new FPGrowthAlg();
        } else if (AlgEnum.PREFIX_SPAN.getVal() == type) {
            return new PrefixSpanAlg();
        } else if (AlgEnum.GRAPH_BUILD.getVal() == type) {
            return new GraphAlg();
        }
        return null;
    }

    protected void prepareOutputColumnTypes(JSONArray outputColumnType, List<String> columnKeys,
                                            List<String> columnNames, List<String> columnTypes) {
        if (columnKeys == null || columnNames == null || columnTypes == null) {
            return;
        }
        for (String key : columnKeys) {
            String type = ToolUtil.getSpecColumnType(columnNames, columnTypes, key);
            outputColumnType.add(type);
        }
    }

    protected void checkBoxSelectFilter(JSONObject jsonObject, String filterType, String key) {
        if (jsonObject.containsKey("input")) {
            JSONArray input = jsonObject.getJSONArray("input");
            if (input != null && input.size() > 0) {
                JSONArray newFeatures = new JSONArray();
                for (int i = 0; i < input.size(); ++i) {
                    JSONObject rawItem = input.getJSONObject(i);
                    JSONObject item = SerializationUtils.clone(rawItem);
                    List<String> tableCols = item.getJSONArray("tableCols")
                            .toJavaList(String.class);
                    List<String> columnTypes = item.getJSONArray("columnTypes")
                            .toJavaList(String.class);
                    Pair<List<String>, List<String>> pair = ToolUtil
                            .filterTypeAndCol(tableCols, columnTypes, filterType);
                    item.put("tableCols", pair.getKey());
                    item.put("columnTypes", pair.getValue());
                    newFeatures.add(item);
                }
                jsonObject.put(key, newFeatures);
            }
        }
    }

    protected void supplementForSelector(JSONObject jsonObject, String tplFileName, int index, TaskVO vo) {
        this.supplementForSelector(jsonObject, tplFileName, index, vo, "");
    }


    /**
     * @param jsonObject
     * @param tplFileName
     * @param index       selector's index in template file
     * @param vo
     * @param filterType  ["", "float", "date", "string", "int"]
     */
    protected void supplementForSelector(JSONObject jsonObject, String tplFileName, int index, TaskVO vo,
                                         String filterType) {
        JSONArray setParams = jsonObject.getJSONArray("setParams");
        List<String> inputCols = jsonObject.getJSONArray("input").getJSONObject(0)
                .getJSONArray("tableCols").toJavaList(String.class);
        List<String> columnTypes = jsonObject.getJSONArray("input").getJSONObject(0)
                .getJSONArray("columnTypes").toJavaList(String.class);
        if (setParams == null) {
            setParams = this.getTemplateParamList(tplFileName);
        }

        List<String> filterCols = ToolUtil.filterTypeAndCol(inputCols, columnTypes, filterType).getKey();
        if (filterType.equals("bool")) {

        }

        JSONObject featureColTpl = setParams.getJSONObject(index);
        if (featureColTpl.getJSONArray("items").size() == 0 && filterCols.size() != 0) {
            logger.debug("items is empty");
            featureColTpl.put("items", filterCols);
            featureColTpl.put("default", filterCols.get(0));
            featureColTpl.put("value", filterCols.get(0));
            setParams.set(index, featureColTpl);
            jsonObject.put("setParams", setParams);
            vo.setData(jsonObject);
        }
    }

    protected void supplementForCheckbox(JSONObject jsonObject, String tplFileName, int index, TaskVO vo) {
        this.supplementForCheckbox(jsonObject, tplFileName, index, vo, FEATURE_COLS);
    }

    protected Set<String> getSetForNewFeatures(JSONArray array) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < array.size(); ++i) {
            JSONObject item = array.getJSONObject(i);
            String tableName = item.getString("tableName");
            List<String> tableCols = item.getJSONArray("tableCols").toJavaList(String.class);
            for (String e : tableCols) {
                set.add(String.format("%s.%s", tableName, e));
            }
        }
        return set;
    }

    protected void supplementForCheckbox(JSONObject jsonObject, String tplFileName, int index, TaskVO vo, String key) {
        JSONArray setParams = jsonObject.getJSONArray("setParams");
        if (setParams == null) {
            setParams = this.getTemplateParamList(tplFileName);
        }
        JSONObject featureColTpl = setParams.getJSONObject(index);
        featureColTpl.put(FEATURE_COLS, jsonObject.getJSONArray(key));
        Set<String> set = this.getSetForNewFeatures(jsonObject.getJSONArray(key));
        JSONArray value = featureColTpl.getJSONArray("value");
        Iterator<Object> iter = value.iterator();
        while (iter.hasNext()) {
            String item = iter.next().toString();
            if (!set.contains(item)) {
                iter.remove();
            }
        }
        if (value.size() == 0) {
            // if checkbox is empty , then default options all selected
            value.addAll(set);
        }
        featureColTpl.put("value", value);
        setParams.set(index, featureColTpl);
        jsonObject.put("setParams", setParams);
        vo.setData(jsonObject);
    }

    public EngineEnum getEngine() {
        return EngineEnum.valueOf(this.engineName.toUpperCase());
    }
}